/**
 * DOM操作封装
 */
export class DomUtils {

    /**
     * 页面dom加载完毕后执行回调
     * @param {Function} fn 回调函数
     */
    static ready(fn) {
        if (document.readyState === 'interactive') {
            fn();
            return;
        }
        let isRun = false;
        document.onreadystatechange = function() {
            if (document.readyState === 'interactive') {
                fn();
                isRun = true;
            }
            if (document.readyState === 'complete' && !isRun) {
                fn();
            }
        }
    }

    /**
     * 将页面滚动到顶部
     * @param {ScrollToOptions} options 滚动配置
     */
    static scrollToTop(options = { top: 0, behavior: 'instant' }) {
        window.scrollTo(options);
    }

    /**
     * 删除元素
     * @param {SelectorType} selector css选择器
     */
    static remove(selector) {
        selectorTypeBranch(selector, {
            string() {
                document.querySelector(selector)?.remove();
            },
            element() {
                selector?.remove();
            },
            array() {
                selector.forEach(item => {
                    if (typeof item === 'string') {
                        item = document.querySelector(item);
                    }
                    item?.remove();
                });
            }
        });
    }

    /**
     * 使用定时器删除多个元素
     * @param {string[]} arr css选择器数组
     * @param {number} duration 定时器延时时间，默认500ms
     */
    static intervalRemove(arr, duration = 500) {
        // 自己调用就不校验了
        // if (!Array.isArray(arr) || arr.length === 0) return;

        // 循环次数
        let iteration = 0;
        let timeId = 0;
        const fn = function() {
            arr = arr.filter(item => {
                const element = document.querySelector(item);
                element && element.remove();
                return !element;
            });
            if (arr.length === 0 || iteration >= 5) {
                timeId && clearInterval(timeId);
            }
            iteration++;
        }
        fn();   // 立即执行一次
        timeId = setInterval(fn, duration);
        return this;
    }

    /**
     * 选择器类型
     * @typedef {string | (string | Element)[] | Element} SelectorType
     */

    /**
     * 隐藏元素
     * @param {string | string[] | Element} selector 可以是css选择器字符串、css选择器字符串数组或html元素对象
     * @param {'display' | 'scale'} hideType 元素的隐藏方式。display表示修改display，scale表示缩小为0
     */
    static hide(selector, hideType = 'display') {
        const fn = {
            /**
             * @param {Element} el 
             */
            display(el) {
                if (el) el.style.display = 'none';
            },
            /**
             * @param {Element} el 
             */
            scale(el) {
                if (el) {
                    Object.assign(el.style, {
                        transform: 'scale(0)',
                        position: 'fixed',
                        zIndex: -999
                    });
                }
            }
        }[hideType];
        selectorTypeBranch(selector, {
            string() {
                fn(document.querySelector(selector));
            },
            array() {
                selector.forEach(item => {
                    if (typeof item === 'string') {
                        fn(document.querySelector(item));
                    } else {
                        fn(item);
                    }
                });
            },
            element() {
                fn(selector);
            }
        });
        return this;
    }

    /**
     * 删除元素的class
     * @param {string | Element} selector css选择器字符串
     * @param {string | string[]} classList 要删除的class0
     */
    static removeClass(selector, classList) {
        let el;
        selectorTypeBranch(selector, {
            string() {
                el = document.querySelector(selector);
            },
            element() {
                el = selector;
            }
        });
        if (!el) return;
        selectorTypeBranch(classList, {
            string() {
                el.classList.remove(classList);
            },
            array() {
                classList.forEach(item => el.classList.remove(item));
            }
        });
    }

    /**
     * 为元素设置css
     * @param {SelectorType} selector 可以是css选择器字符串、css选择器字符串数组或html元素对象
     * @param {CSSStyleDeclaration} obj css对象
     */
    static setCss(selector, obj = {}) {
        selectorTypeBranch(selector, {
            string() {
                const el = document.querySelector(selector);
                el && Object.assign(el.style, obj);
            },
            array() {
                selector.forEach(item => {
                    if (typeof item === 'string' && item.length) {
                        item = document.querySelector(item);
                    }
                    item && Object.assign(item.style, obj);
                });
            },
            element() {
                Object.assign(selector.style, obj);
            }
        });
        return this;
    }

    /**
     * 向页面中添加自定义样式
     * @param {string} style CSS样式字符串
     */
    static appendStyle(style) {
        const styleElement = document.createElement('style');
        styleElement.innerHTML = style;
        document.body.appendChild(styleElement);
    }

    /**
     * @typedef {Object} ObserverResult
     * @property {MutationObserver} observer MutationObserver实例，可以用于取消监听(observer.disconnect())
     * @property {MutationRecord[]} mutationsList 元素的哪些内容发生了变化
     * @property {Element} target 监听的元素
     * @property {number} index 一个初始值为0的索引
     * 
     * 使用 MutationObserver 监听元素
     * @param {string | Element} selector css选择器或html元素对象
     * @param {MutationRecordType[]} observeType 监听元素的哪些属性，参考MutationRecordType，可以监听多个属性
     * @param {MutationObserverInit} observeConfig 监听器调用observe时传入的配置。参考https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver/observe
     * @param {(result: ObserverResult) => void } callback 回调函数
     */
    static observe(selector, observeType, observeConfig, callback) {
        // 监听目标元素
        let targetNode = selector;
        if (typeof selector === 'string' && selector.length) {
            targetNode = document.querySelector(selector);
        }
        if (!targetNode) return;

        // 创建一个 Mutation Observer 实例，并指定回调函数
        const observer = new MutationObserver((mutationsList, observer) => {
            // 遍历每一个变动（mutation）
            for (let mutation of mutationsList) {
                if (observeType.includes(mutation.type)) {
                    callback({ observer, mutationsList, target: targetNode, index: 0 });
                }
            }
        });

        // 开始观察目标节点及其子节点的变化
        observer.observe(targetNode, observeConfig);
    }

    /**
     * 观察某个父元素，当子元素发生变化时隐藏子元素或者执行执行的回调函数
     * @param {string | Element} root 父元素，observer观察对象
     * @param {string | (string | Record<string, (el: Element) => void>)[] | Record<string, (el: Element) => void>} child 需要隐藏的子元素，可以是css选择器字符串、css选择器字符串数组 或 回调函数对象
     * @param {{hideType: 'display' | 'scale' | 'remove', loop: boolean}} options 隐藏方式和是否循环监听
     * @param {'display' | 'scale' | 'remove'} options.hideType 隐藏方式，默认为display
     * @param {boolean} options.loop 是否循环监听，默认为false
     */
    static observeHide(root, child, { hideType, loop } = { hideType: 'display', loop: false }) {
        DomUtils.observe(root, ['childList'], { childList: true }, ({ observer, target, index }) => {
            // 当 child 为字符串时，隐藏或删除指定的元素
            if (typeof child === 'string' && child.length) {
                let element = target.querySelector(child);
                if (element) {
                    hideType === 'remove' ? element.remove() : DomUtils.hide(element, hideType);
                    if (!loop) observer.disconnect();
                }
                return;
            }
            // 当 child 为数组时，隐藏或删除指定的元素
            if (Array.isArray(child) && Array.length > 0) {
                child.forEach(selector => {
                    if (typeof selector === 'string' && selector.length) {
                        let element = target.querySelector(selector);
                        if (element) {
                            hideType === 'remove' ? element.remove() : DomUtils.hide(element, hideType);
                            index++;
                        }
                        return;

                    }
                    if (typeof selector === 'object' && Object.keys(selector).length > 0) {
                        const key = Object.keys(selector)[0]
                        const el = target.querySelector(key);
                        if (el) {
                            selector[key](el);
                            index++;
                        }
                    }
                });
                if (index >= child.length && !loop) observer.disconnect();
                return;
            } 
            // 当 child 为对象时，执行回调函数
            if (typeof child === 'object' && Object.keys(child).length > 0) {
                Object.keys(child).forEach(selector => {
                    let element = target.querySelector(selector);
                    if (element) {
                        child[selector](element);
                        index++;
                    }
                });
                if (index >= Object.keys(child).length && !loop) observer.disconnect();
            }
        });
    }

    /**
     * 使用IntersectionObserver监听元素是否进入或离开视口
     * @param {Element | Element[]} target 目标元素
     * @param {(result: { entries: IntersectionObserverEntry[], observer: IntersectionObserver }) => void} callback 回调函数
     * @param {IntersectionObserverInit} options 
     * @param {Element | null} options.root 指定目标元素所在的最外层容器，默认是视口
     * @param {number} options.rootMargin 目标元素距离视口（或根元素的）边界的偏移，默认是0px
     * @param {number} options.threshold 目标元素与视口（或根元素的）交叉区域的比例，默认是0
     */
    static intersectionObserve(target, callback, options = {}) {
        const observer = new IntersectionObserver(entries => {
            callback({ entries, observer });
        }, options);
        if (Array.isArray(target)) {
            target.forEach(item => observer.observe(item));
        } else {
            observer.observe(target);
        }
    }

    /**
     * 处理评论区域。监听评论数量，根据评论数量显示或隐藏评论区域
     * @param {string | Element} comments 评论区域根元素或css选择器
     * @param {string | Element} commentCount 包含评论数量的元素或css选择器
     * @param {string | Element} input 评论输入框的元素对象或css选择器
     * @param {string} zeroText 评论数量为0时显示的文本
     * @param {(comments: Element, commentCount: Element, input: Element) => void} callback 回调函数，在评论数量不为0时执行
     */
    static handleComment(comments, commentCount, input, zeroText, callback = () => {}) {
        // 评论区域根元素
        if (typeof comments === 'string' && comments.length) {
            comments = document.querySelector(comments);
        }
        if (!comments) return;

        // 包含评论数量的元素
        if (typeof commentCount === 'string' && commentCount.length) {
            commentCount = comments.querySelector(commentCount);
        }
        if (!commentCount) return;

        // 如果评论数量为0，则隐藏评论区域
        if (commentCount.textContent === zeroText) {
            DomUtils.hide(comments);
        } else {
            // 隐藏评论输入框
            if (typeof input === 'string' && input.length) {
                input = comments.querySelector(input);
            }
            if (input) DomUtils.hide(input);    
            callback({ comments, commentCount, input });
        }

        // 监听评论数量是否发生变化，如果发生变化则根据评论数量显示或隐藏评论区域
        DomUtils.observe(commentCount.firstChild, ['characterData'], { characterData: true }, ({ observer }) => {
            // 如果评论数量为0，则隐藏评论区域
            if (commentCount.textContent === zeroText) {
                DomUtils.hide(comments);
            } else {
                comments.style.display = 'block';
                // 隐藏评论输入框
                if (typeof input === 'string' && input.length) {
                    input = comments.querySelector(input);
                }
                if (input) DomUtils.hide(input);
                callback({ comments, commentCount, input });

                observer.disconnect();
            }
        });
    }
}

/**
 * 根据 selector 参数的类型，调用不同的函数 
 * @param {string | Element | (string | Element)[] } selector 可以是css选择器字符串、css选择器字符串数组或html元素对象
 * @param {{ string: () => void, array: () => void, element: () => void }} obj 回调函数集合
 * @param {() => void} obj.string 当 selector 为 string 类型时，执行该函数
 * @param {() => void} obj.array 当 selector 为 string[] 类型时，执行该函数
 * @param {() => void} obj.element 当 selector 为 Element 类型时，执行该函数
 */
function selectorTypeBranch(selector, obj) {
    if (!selector) return;

    // 如果 selector 是一个字符串
    if (typeof selector === 'string' && selector.length > 0) {
        return obj.string && obj.string();
    }

    // 如果 selector 是一个字符串数组
    if (Array.isArray(selector) && selector.length > 0) {
        return obj.array && obj.array();
    }
    // 如果 selector 是一个html元素对象
    if (selector instanceof Element) {
        return obj.element && obj.element();
    } 
}